﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using FFmpeg.AutoGen;
using FFmpegVideoClip.Lib;
using FFmpegVideoClip.Lib.AdvancedFragmentProcessor;
using FFmpegVideoClip.Mode;
using PropertyChanged;

namespace FFmpegVideoClip.Configuration
{
    [AddINotifyPropertyChangedInterface,Serializable]
    public class Config
    {
        public const string Video = "Video";
        public const string Audio = "Audio";
        private const string FileName = "Configuration.config";
        private static string? m_FilePath;
        private static string FilePath
        {
            get
            {
                if (string.IsNullOrEmpty(m_FilePath))
                    m_FilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FileName);
                return m_FilePath;
            }
        }

        public static void XmlSerializerToXmlFile<T>(string Path, T t, bool EnableIndent = false)
        {
            using (XmlWriter xw = XmlWriter.Create(Path,
            new XmlWriterSettings { Encoding = Encoding.UTF8, CloseOutput = true, Indent = EnableIndent }))
            {
                XmlSerializer xs = new XmlSerializer(typeof(T));
                xs.Serialize(xw, t);
            }
        }

        public static T PathToXmlDeserialize<T>(string Path)
        {
            using (XmlReader xr = XmlReader.Create(Path))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                return (T)serializer.Deserialize(xr)!;
            }
        }
        private static void CheckProcessors()
        {
            foreach (CallProcessor processor in Instance.Processors)
            {
                if (processor.ID < 0)
                    processor.ID = Instance.Processors.Max(cp => cp.ID) + 1;
            }
        }
        private static void UpdatePresetSchemeModule()
        {
            foreach (AdvancedParametersSchemeModule flow in Instance.Presets.SelectMany(fs => fs.Flows))
                Instance.SchemeModules.FirstOrDefault(p => p.SchemeID == flow.SchemeID)?.CopyTo(flow, false);
            foreach (AdvancedParametersSchemeModule flow in Instance.Presets.SelectMany(fs => fs.AfterprocessingFlows))
                Instance.SchemeModules.FirstOrDefault(p => p.SchemeID == flow.SchemeID)?.CopyTo(flow, false);
        }
        public static bool Save(Window? window = null)
        {
            if (Instance is null)
            {
                Instance = new Config();
            }

            window ??= Application.Current.MainWindow;
            CheckProcessors();
            UpdatePresetSchemeModule();
            
            if (Instance.Processors.CheckDuplicate(p=>p.ProcessorName,StringComparer.OrdinalIgnoreCase))
            {
                MessageBoxEx.Show(window, "处理器配置中存在重复项，无法完成保存", "配置错误", MessageBoxButton.OK, MessageBoxImage.Error);
                return false;
            }

            if (Instance.SchemeModules.CheckDuplicate(p => p.SchemeName, StringComparer.OrdinalIgnoreCase))
            {
                MessageBoxEx.Show(window, "方案配置中存在重复项，无法完成保存", "配置错误", MessageBoxButton.OK, MessageBoxImage.Error);
                return false;
            }

            XmlSerializerToXmlFile(FilePath, Instance, true);

            ClipManage.UpdateClipsSchemeModule();

            return true;
        }

        public static void Load()
        {
            if (!File.Exists(FilePath))
            {
                Save();
                return;
            }
            Instance = PathToXmlDeserialize<Config>(FilePath);
        }

        public static Config Instance { get; private set; }

        public static string CacheDirectory 
        {
            get
            {
                if (string.IsNullOrEmpty(Instance.TempCacheDirectory) || !Directory.Exists(Path.GetPathRoot(Instance.TempCacheDirectory)))
                    throw new ArgumentException("配置文件缓存路径参数错误");
                if (!Directory.Exists(Instance.TempCacheDirectory))
                    Directory.CreateDirectory(Instance.TempCacheDirectory);
                return Instance.TempCacheDirectory;
            }
            set
            {
                if (Instance.TempCacheDirectory.Equals(value, StringComparison.OrdinalIgnoreCase))
                    return;
                if (!Directory.Exists(Path.GetPathRoot(Instance.TempCacheDirectory)))
                {
                    MessageBoxEx.Show(Application.Current.MainWindow, "不支持的路径", "路径错误", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
                Instance.TempCacheDirectory = value;
            }
        }
        public static Fragment.ShowTimeModeEnum CurrentShowTimeMode => Instance.ShowTimeMode;
        public static TimeSpan MinSplitTimeSecondValue => TimeSpan.FromSeconds(Instance.MinSplitTimeSecond);
        public static TimeSpan OpenOutTimeSecondValue => TimeSpan.FromSeconds(Instance.OpenOutTimeSecond);
        public static TimeSpan RemainingTimeStatisticsDurationValue => TimeSpan.FromSeconds(Instance.RemainingTimeStatisticsDuration);

        public static bool IsEnableMinSplitTimeCheck => Instance.EnableMinSplitTimeCheck;
        public static bool IsEnableWriteLog => Instance.EnableLogWrite;
        public static SplitTools.SplitModeEnums SplitMode => Instance.SplitModeValue;
        public static int DragInFileBoundsCount => Instance.DragInFileCountBounds;
        public static AVHWDeviceType ThumbnailHardwareDecoding =>Instance.EnableThumbnailHardwareDecoding;
        public static float TimeLineUpdateRangeEdgeExpansionValue => Instance.TimeLineUpdateRangeEdgeExpansion;
        public static TimeSpan[] ZoomValues => Instance.ZoomLevelValues;
        public static double TickLineHitTestSizeValue =>Instance.TickLineHitTestSize;
        public static readonly Regex FFmpegSuccessValidated = new(Instance?.FFmpegSuccessValidatedExpression ?? @"video:\d*\s*ki?B\s*audio:\s*\d*ki?B\s*subtitle:\s*\d*\s*ki?B", RegexOptions.Compiled | RegexOptions.IgnoreCase);

        public static int MaxThread => Instance.MaxDegreeOfParallelism;
        public static TaskSchedulerEnum TaskScheduler => Instance.TaskSchedulerValue;
        public static int CpuPerformanceCountCache => Instance.CpuPerformanceCountCacheSize;
        public static int CpuPerformanceLimit => Instance.CpuPerformanceLimitValue;
        public static int TaskPollingInterval => Instance.TaskPollingIntervalMillisecond;
        public static bool AutoClearCache => Instance.AutoTempCacheClear;
        public static string CoreProcessor => Instance.CoreCallProcessor;
        public static void AddSchemeModule(Window window, AdvancedParametersSchemeModule value, int Index = -1)
        {
            Guid[] old = Instance.SchemeModules.Select(p => p.SchemeID).ToArray();
            value.SchemeID = Guid.NewGuid();
            while (old.Any(p => p == value.SchemeID))
                value.SchemeID = Guid.NewGuid();

            if (Instance.SchemeModules.Any(p => p.SchemeName.Equals(value.SchemeName, StringComparison.OrdinalIgnoreCase)))
            {
                MessageBoxEx.Show(window, "已存在相同名称的方案", "命名错误", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            else if(Index < 0)
                Instance.SchemeModules.Add(value);
            else
                Instance.SchemeModules.Insert(Index, value);
        }
        public static void AddProcessor(Window window, CallProcessor value)
        {
            if (Instance.Processors.Any(p => p.ProcessorName.Equals(value.ProcessorName, StringComparison.OrdinalIgnoreCase)))
            {
                MessageBoxEx.Show(window, "处理器已存在", "命名错误", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            else
                Instance.Processors.Add(value);
        }
        public static CallProcessor? GetCallProcessor(string ProcessorName)=> Instance.Processors.FirstOrDefault(p=>p.ProcessorName.Equals(ProcessorName,StringComparison.OrdinalIgnoreCase));
        public static CallProcessor? GetCallProcessor(int ID)=> Instance.Processors.FirstOrDefault(p=>p.ID == ID);
        public static CallProcessor? GetCallProcessorOfIdName(string IDName) => Instance.Processors.FirstOrDefault(p => p.IDName.Equals(IDName, StringComparison.OrdinalIgnoreCase));
        public static CallProcessor MainCoreProcessor => Instance.Processors.First(p => p.ProcessorName.Equals(CoreProcessor, StringComparison.OrdinalIgnoreCase));
        public static string VideoDetectProgressValueExpressionValue => Instance.VideoDetectProgressValueExpression;
        public static string BlackdetectRegexExpressionValue => Instance.BlackdetectRegexExpression;
        public static string FreezedetectRegexExpressionValue => Instance.FreezedetectRegexExpression;
        public static string ScdetRegexExpressionValue => Instance.ScdetRegexExpression;
        public static string SelectGtSceneRegexExpressionValue => Instance.SelectGtSceneRegexExpression;
#pragma warning disable CS8618
        static Config() { Load(); }
#pragma warning restore CS8618

        /***************************************************************************************************************************/
        [XmlAttribute("noNamespaceSchemaLocation",Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
        public string noNamespaceSchemaLocation = "Configuration.xsd";
        public string TempCacheDirectory { get; set; } = string.Empty;
        public bool AutoTempCacheClear { set; get; } = false;
        public int MaxDegreeOfParallelism { get; set; } = 6;
        public Fragment.ShowTimeModeEnum ShowTimeMode { get; set; } = Fragment.ShowTimeModeEnum.TimeFormat;
        public TaskSchedulerEnum TaskSchedulerValue { get; set; } = TaskSchedulerEnum.Default;
        public int CpuPerformanceCountCacheSize { get; set; } = 40;
        public int CpuPerformanceLimitValue { get; set; } = 73;
        public int TaskPollingIntervalMillisecond { get; set; } = 3000;
        public double SchemeSetListDragScrollSpeed = 3;
        public bool EnableMinSplitTimeCheck { get; set; } = true;
        public int MinSplitTimeSecond { get; set; } = 15;
        public int OpenOutTimeSecond { get; set; } = 5;
        public bool EnableLogWrite { get; set; } = false;
        public SplitTools.SplitModeEnums SplitModeValue { get; set; } = SplitTools.SplitModeEnums.NextFragmentStart;
        public int RemainingTimeStatisticsDuration { get; set; } = 30;
        public int DragInFileCountBounds { get; set; } = 3;
        private float m_TimeLineUpdateRangeEdgeExpansion = 30;
        public float TimeLineUpdateRangeEdgeExpansion { get => m_TimeLineUpdateRangeEdgeExpansion; set => m_TimeLineUpdateRangeEdgeExpansion = value < 10 ? 10 : value; }
        public string CoreCallProcessor { get; set; } = "ffmpeg.exe";
        public AVHWDeviceType EnableThumbnailHardwareDecoding { get; set; } = AVHWDeviceType.AV_HWDEVICE_TYPE_NONE;
        public double[] ZoomLevelValuesSeconds { get; set; } = { 300d, 120d, 30d, 7.5d, 5d, 1d };
        private TimeSpan[]? m_ZoomLevelValues = null;
        public TimeSpan[] ZoomLevelValues => m_ZoomLevelValues ??= ZoomLevelValuesSeconds.Select(p => TimeSpan.FromSeconds(p)).ToArray();
        public double TickLineHitTestSize { get; set; } = 3;
        public string FFmpegSuccessValidatedExpression { get; set; } = @"(?i)video:\d*\s*ki?B\s*audio:\s*\d*ki?B\s*subtitle:\s*\d*\s*ki?B\s*other\s*streams:\s*\d*\s*ki?B\s*global\s*headers:\s*\d*\s*ki?B\s*muxing\s*overhead";
        public string VideoDetectProgressValueExpression { get; set; } = @"(?<=(.+)?frame.+time=)(\d+:)?(\d{1,2}:)?(\d{1,2})?(\.\d+)?";
        public string BlackdetectRegexExpression { get; set; } = @"(?<=.+blackdetect.+black_start:)(?<start>\d+(\.\d{1,3})?)(?:.+black_end:)(?<end>\d+(\.\d{1,3})?)";
        public string FreezedetectRegexExpression { get; set; } = @"(?<=.+freezedetect.+freeze_)(?:start:.+?)(?<start>\d+(\.\d{1,3})?)|(?:end:.+?)(?<end>\d+(\.\d{1,3})?)";
        public string ScdetRegexExpression { get; set; } = @"(?<=.+scdet.+time:.+)\d+(\.\d{1,3})?";
        public string SelectGtSceneRegexExpression { get; set; } = @"(?<=pts_time\:)\d+(\.\d{1,3})?";
        [XmlArrayItem("Item")]
        public ObservableCollection<AdvancedParametersSchemeModule> SchemeModules { get; set; } = new();

        public ObservableCollection<CallProcessor> Processors { get; set; } = new();

        public ObservableCollection<Preset> Presets { get; set; } = new();

        [XmlIgnore] public Preset[] NoTimeParamPresets => Presets.Where(p => p.AfterprocessingFlows.Count < 1 &&
            p.Flows.All(f => !(f.GetParametersSign(ParametersSign.STime, out _) || f.GetParametersSign(ParametersSign.ETime, out _)))).ToArray();
    }

    [Serializable, AddINotifyPropertyChangedInterface]
    public class CallProcessor : IXmlSerializable
    {
        /// <summary>
        /// 处理器名称-带.exe
        /// </summary>
        public string ProcessorName { get; set; } = string.Empty;
        public int ID { get; set; } = -1;
        public string IDName => $"ID{ID}";
        /// <summary>
        /// 验证处理器成功完成的表达式
        /// </summary>
        public string SuccessValidatedExpression { get; set; } = string.Empty;

        /// <summary>
        /// 将完成进度计算成完成时间的c#脚本
        /// </summary>
        public string ProgressScript { get; set; } = string.Empty;

        /// <summary>
        /// 获取进度值表达式
        /// </summary>
        public string GetProgressValueExpression { get; set; } = string.Empty;

        /// <summary>
        /// 是否使用持续时间模式
        /// </summary>
        public bool DurationMode { get; set; } = false;

        public XmlSchema? GetSchema() => null;

        public void ReadXml(XmlReader reader)
        {
            reader.MoveToContent();
            if (!reader.IsEmptyElement)
            {
                ProcessorName = reader.GetAttribute("ProcessorName")!;
                DurationMode = bool.Parse(reader.GetAttribute("DurationMode")!);
                ID = int.Parse(reader.GetAttribute("ID")!);
                reader.ReadStartElement();


                reader.ReadStartElement("SuccessValidatedExpression");
                SuccessValidatedExpression = reader.ReadContentAsString();
                reader.ReadEndElement();

                reader.ReadStartElement("ProgressScript");
                ProgressScript = reader.ReadContentAsString();
                reader.ReadEndElement();

                reader.ReadStartElement("GetProgressValueExpression");
                GetProgressValueExpression = reader.ReadContentAsString();
                reader.ReadEndElement();

                reader.ReadEndElement();
            }
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteAttributeString("ProcessorName", ProcessorName);
            writer.WriteAttributeString("ID", ID.ToString());
            writer.WriteAttributeString("DurationMode", DurationMode.ToString());

            writer.WriteStartElement("SuccessValidatedExpression");
            writer.WriteCData(SuccessValidatedExpression);
            writer.WriteEndElement();

            writer.WriteStartElement("ProgressScript");
            writer.WriteCData(ProgressScript);
            writer.WriteEndElement();

            writer.WriteStartElement("GetProgressValueExpression");
            writer.WriteCData(GetProgressValueExpression);
            writer.WriteEndElement();
        }
        

    }


    [Serializable, AddINotifyPropertyChangedInterface]
    public class Preset : IDeepCopy<Preset>
    {
        public bool IsSplittedTitle { get; set; } = false;
        public string Name { get; set; }= string.Empty;
        public ObservableCollection<AdvancedParametersSchemeModule> Flows { get; set; }
        public ObservableCollection<AdvancedParametersSchemeModule> AfterprocessingFlows { get; set; } = new();

        public Preset()
        {
            Flows = new();
            Flows.CollectionChanged += Flows_CollectionChanged;
        }

        private void Flows_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            int index = 0;
            foreach (AdvancedParametersSchemeModule item in Flows)
                item.UIIndex = index++;
        }

        public void SetFlows(IEnumerable<AdvancedParametersSchemeModule> FlowsModules, IEnumerable<AdvancedParametersSchemeModule> AfterprocessingFlowsModules)
        {
            Flows.Clear();
            foreach (AdvancedParametersSchemeModule module in FlowsModules)
                Flows.Add(module.DeepCopy());
            AfterprocessingFlows.Clear();
            foreach (AdvancedParametersSchemeModule module in AfterprocessingFlowsModules)
                AfterprocessingFlows.Add(module.DeepCopy());
        }

        public void SetTo(ObservableCollection<AdvancedParametersSchemeModule> FlowsCollection, ObservableCollection<AdvancedParametersSchemeModule> AfterprocessingFlowsCollection)
        {
            FlowsCollection.Clear();
            AfterprocessingFlowsCollection.Clear();
            if (Flows.Count > 0)
            {
                foreach (AdvancedParametersSchemeModule module in Flows)
                    FlowsCollection.Add(module.DeepCopy());
            }
            if (AfterprocessingFlows.Count > 0)
            {
                foreach (AdvancedParametersSchemeModule module in AfterprocessingFlows)
                    AfterprocessingFlowsCollection.Add(module.DeepCopy());
            }
        }

        public Preset DeepCopy()
        {
            Preset value = new Preset();
            IsSplittedTitle = value.IsSplittedTitle;
            value.Name = Name;
            SetTo(value.Flows,value.AfterprocessingFlows);
            return value;
        }
    }

}
